梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
回溯算法的本质:它是一种深度优先搜索(DFS)的暴力枚举策略,核心是“尝试 - 回退”—— 在解决问题的过程中,一步步尝试所有可能的选择,当发现当前选择无法得到合法解时,就回退到上一步,撤销当前选择,继续尝试其他可能,直到找到所有解 / 一个合法解。
问题描述:
算法解析:
#include <iostream>
#include <vector>
using namespace std;
vector<vector<string>> res_palindrome;
vector<string> path_palindrome;
// 校验s[start..end]是否为回文串(逐字符对比)
bool isPalindrome(const string& s, int start, int end) {
while (start < end) {
if (s[start++] != s[end--]) return false;
}
return true;
}
// 回溯函数:分割回文串
void palindrome(const string& s, int start) {
if (start == s.size()) {
res_palindrome.push_back(path_palindrome);
return;
}
for (int i = start; i < s.size(); ++i) {
if (!isPalindrome(s, start, i)) continue;
path_palindrome.push_back(s.substr(start, i - start + 1));
palindrome(s, i + 1);
path_palindrome.pop_back();
}
}
int main() {
string s="aab";
palindrome(s,0);
for(vector<string> item : res_palindrome)
{
cout << "[";
for(string node : item )
{
cout << " "<< node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ a a b ]
[ aa b ]
问题描述:
算法解析:
IP 地址的约束非常明确,回溯的核心是「分割字符串为 4 个合法段」,具体思路:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<string> res_ip; // 存储所有有效IP地址
// 回溯函数:
// s: 原始字符串
// start: 当前分割的起始索引
// path: 当前拼接的IP路径(如 "192.168")
// count: 已分割的段数(目标是4段)
void ipAddresses(const string& s, int start, string path, int count) {
// 剪枝:段数超过4,直接返回
if (count > 4) return;
// 终止条件:段数=4且遍历完字符串,找到有效IP
if (count == 4 && start == s.size()) {
res_ip.push_back(path);
return;
}
// 遍历当前段的可能长度:1~3个字符(IP段最大长度为3)
for (int len = 1; len <= 3; ++len) {
// 剪枝:截取长度超出字符串范围,直接break
if (start + len > s.size()) break;
// 截取当前段
string seg = s.substr(start, len);
// 校验当前段是否合法:
// 1. 长度>1时,不能以'0'开头(如"01"非法)
// 2. 数值必须≤255
if ((len > 1 && seg[0] == '0') || (len == 3 && stoi(seg) > 255)) {
continue;
}
// 拼接路径:非最后一段需加'.',最后一段不加
string newPath = path + seg + (count == 3 ? "" : ".");
// 递归:处理下一段,段数+1,起始索引+当前段长度
ipAddresses(s, start + len, newPath, count + 1);
// 回溯:无需显式还原(path是值传递,递归返回后自动恢复)
}
}
int main() {
string s="25525511135";
// 剪枝:IP地址总长度范围是4~12(4段×1位 ~ 4段×3位)
if (s.size() < 4 || s.size() > 12) return;
// 从起始索引0、空路径、0段开始回溯
ipAddresses(s, 0, "", 0);
for(string item : res_ip)
{
cout << " [";
cout << " " << item << " ";
cout << "] " << endl;
}
return 0;
}
[ 255.255.11.135 ]
[ 255.255.111.35 ]
问题描述:
算法解析:
直接回溯会因重复子问题导致超时(比如多次处理 s[5..n] 的拆分),因此需要记忆化搜索(Memoization) 优化,核心思路:
关键优化点:
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <unordered_map>
using namespace std;
unordered_set<string> wordSet; // 字典的哈希集合,快速校验单词
unordered_map<int, vector<string>> memoVal; // 记忆化:memo[start]存储s[start..]的拆分结果
int maxWordLen; // 字典中最长单词的长度,用于剪枝
// 递归函数:返回s[start..n-1]的所有合法拆分方案
vector<string> wordBreak(const string& s, int start) {
// 记忆化:已计算过s[start..]的结果,直接返回
if (memoVal.count(start)) return memoVal[start];
// 终止条件:start到达末尾,返回包含空字符串的列表(用于拼接)
if (start == s.size()) return {""};
vector<string> res; // 存储s[start..]的所有拆分结果
// 遍历截取的结束位置,剪枝:最多截取到start + maxWordLen
for (int i = start; i < s.size() && i < start + maxWordLen; ++i) {
string word = s.substr(start, i - start + 1);
// 候选单词不在字典中,跳过
if (!wordSet.count(word)) continue;
// 递归处理剩余子串s[i+1..]
vector<string> subRes = wordBreak(s, i + 1);
// 拼接当前单词和剩余子串的拆分结果
for (string& sub : subRes) {
if (sub.empty()) {
// 剩余子串为空,直接添加当前单词
res.push_back(word);
} else {
// 拼接:当前单词 + 空格 + 剩余子串的拆分结果
res.push_back(word + " " + sub);
}
}
}
// 记忆化存储:记录s[start..]的拆分结果
memoVal[start] = res;
return res;
}
int main() {
vector<string> res;
string s="catsanddog";
vector<string> wordDict={"cat","cats","and","sand","dog"};
// 1. 初始化字典集合
for (string& word : wordDict) {
wordSet.insert(word);
// 2. 计算字典中最长单词的长度,用于剪枝
maxWordLen = max(maxWordLen, (int)word.size());
}
// 3. 从start=0开始递归拆分
res = wordBreak(s, 0);
for(string item : res)
{
cout << " [";
cout << " " << item << " ";
cout << "] " << endl;
}
return 0;
}
[ cat sand dog ]
[ cats and dog ]